﻿/* Copyright (c) Microsoft Corporation
 * 
 * All rights reserved.
 * 
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License.  You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
 * 
 * THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE, MERCHANTABLITY OR NON-INFRINGEMENT.
 * 
 * See the Apache Version 2.0 License for specific language governing permissions and limitations under the License.
*/

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace ProximityTapper
{
    /// <summary>
    /// Class for OobConnector / OobConnectorAck message
    /// This class can store either SessionActivation or SessionActivationAck message 
    /// depending on the value of IsAck passed during constructor. Depending on this value
    /// it would try to parse messages differently.
    /// </summary>
    [CLSCompliant(false)]
    public class OobConnector : IOobConnector, IMessageData
    {
        private static UInt32 MAX_ID = 8;
        private static UInt32 MAX_ADDRESS = 16;
        private static UInt32 MAX_BLUETOOTH_MAC_ADDRESS = 8;
        private static UInt32 OOB_CONNECTOR_MIN_SIZE = MAX_ID + (6 * MAX_ADDRESS) + MAX_BLUETOOTH_MAC_ADDRESS + sizeof(UInt32) + sizeof(UInt16);
        private static UInt32 OOB_CONNECTOR_ACK_MIN_SIZE = (6 * MAX_ADDRESS) + MAX_BLUETOOTH_MAC_ADDRESS + sizeof(UInt16);

        // This determines if current message is Oob Connector Service Activation message
        // or Oob Connector Service Ack Message
        public bool IsAck { get; private set; }

        // Service Activation Header
        // This is used in OobConnector Message
        private IServiceActivationHeader serviceActivationHeader = new ServiceActivationHeader();

        public IServiceActivationHeader ServiceActivationHeader
        {
            get { return serviceActivationHeader; }
            set { serviceActivationHeader = value; }
        }

        // Reply channel ID (8 bytes)
        // This is used in OobConnector Message
        private byte[] replyChannelId = new byte[MAX_ID];

        public byte[] ReplyChannelId
        {
            get { return replyChannelId; }
            set { replyChannelId = value; }
        }

        // WiFi Direct (WFD) Address (16 bytes)
        // This is used in OobConnector Message
        // Also used in OobConnectorAck Message
        private byte[] wfdAddress = new byte[MAX_ADDRESS];

        public byte[] WFDAddress
        {
            get { return wfdAddress; }
            set { wfdAddress = value; }
        }

        // Link Local Address (16 bytes)
        // This is used in OobConnector Message
        // Also used in OobConnectorAck Message
        private byte[] linkLocalAddress = new byte[MAX_ADDRESS];

        public byte[] LinkLocalAddress
        {
            get { return linkLocalAddress; }
            set { linkLocalAddress = value; }
        }

        // IPv4 Address (16 bytes)
        // This is used in OobConnector Message
        // Also used in OobConnectorAck Message
        private byte[] ipv4Address = new byte[MAX_ADDRESS];

        public byte[] IPv4Address
        {
            get { return ipv4Address; }
            set { ipv4Address = value; }
        }

        // Proximity Address (16 bytes)
        // This is used in OobConnector Message
        // Also used in OobConnectorAck Message
        private byte[] proximityAddress = new byte[MAX_ADDRESS];

        public byte[] ProximityAddress
        {
            get { return proximityAddress; }
            set { proximityAddress = value; }
        }

        // Global Address (16 bytes)
        // This is used in OobConnector Message
        // Also used in OobConnectorAck Message
        private byte[] globalAddress = new byte[MAX_ADDRESS];

        public byte[] GlobalAddress
        {
            get { return globalAddress; }
            set { globalAddress = value; }
        }

        // Teredo Address (16 bytes)
        // This is used in OobConnector Message
        // Also used in OobConnectorAck Message
        private byte[] teredoAddress = new byte[MAX_ADDRESS];

        public byte[] TeredoAddress
        {
            get { return teredoAddress; }
            set { teredoAddress = value; }
        }

        // reserved 1 (32 bits)
        // This is used in OobConnector Message
        public UInt32 Reserved { get; private set; }

        // Bluetooth MAC Address (8 bytes)
        // This is used in OobConnector Message
        // Also used in OobConnectorAck Message
        private byte[] bluetoothMACAddress = new byte[MAX_BLUETOOTH_MAC_ADDRESS];

        public byte[] BluetoothMACAddress
        {
            get { return bluetoothMACAddress; }
            set { bluetoothMACAddress = value; }
        }

        // WFD Blob
        // This is used in OobConnector Message
        // Also used in OobConnectorAck Message
        public byte[] WFDBlob { get; private set; }

        public OobConnector(bool isAck)
        {
            this.IsAck = isAck;
        }

        bool IMessageData.TryParse(byte[] data)
        {
            bool isParsed = true;
            UInt32 offset = 0;
            UInt32 minSize = (this.IsAck) ? OOB_CONNECTOR_ACK_MIN_SIZE : OOB_CONNECTOR_MIN_SIZE;

            try
            {
                if (!this.IsAck)
                {
                    if (!this.ServiceActivationHeader.TryParse(data, ref offset))
                    {
                        isParsed = false;
                    }
                }

                // Check if remaining bytes are greater than or equal to Min size required
                // Note: This size does not include Service Activation Header
                if (minSize <= (data.Length - offset))
                {
                    if (!this.IsAck)
                    {
                        Array.Copy(data, offset, this.ReplyChannelId, 0, MAX_ID);
                        offset += MAX_ID;
                    }

                    Array.Copy(data, offset, this.WFDAddress, 0, MAX_ADDRESS);
                    offset += MAX_ADDRESS;

                    Array.Copy(data, offset, this.LinkLocalAddress, 0, MAX_ADDRESS);
                    offset += MAX_ADDRESS;

                    Array.Copy(data, offset, this.IPv4Address, 0, MAX_ADDRESS);
                    offset += MAX_ADDRESS;

                    Array.Copy(data, offset, this.ProximityAddress, 0, MAX_ADDRESS);
                    offset += MAX_ADDRESS;

                    Array.Copy(data, offset, this.GlobalAddress, 0, MAX_ADDRESS);
                    offset += MAX_ADDRESS;

                    Array.Copy(data, offset, this.TeredoAddress, 0, MAX_ADDRESS);
                    offset += MAX_ADDRESS;

                    if (!this.IsAck)
                    {
                        this.Reserved = BigEndianConverter.ToUInt32(data, offset);
                        offset += sizeof(UInt32);
                    }

                    Array.Copy(data, offset, this.BluetoothMACAddress, 0, MAX_BLUETOOTH_MAC_ADDRESS);
                    offset += MAX_BLUETOOTH_MAC_ADDRESS;

                    UInt16 wfdBlobSize = BigEndianConverter.ToUInt16(data, offset);
                    offset += sizeof(UInt16);

                    if (wfdBlobSize > 0)
                    {
                        // Confirm that we have enough bytes to read WFD Blob
                        if (wfdBlobSize <= (data.Length - offset))
                        {
                            this.WFDBlob = new byte[wfdBlobSize];
                            Array.Copy(data, offset, this.WFDBlob, 0, wfdBlobSize);
                            offset += wfdBlobSize;
                        }
                        else
                        {
                            isParsed = false;
                        }
                    }
                }
            }
            catch (Exception)
            {
                // Parsing Error
                isParsed = false;
            }

            // Make sure that all bytes are read
            if (offset != data.Length)
            {
                isParsed = false;
            }

            return isParsed;
        }

        public override string ToString()
        {
            String output = "";

            if (!this.IsAck)
            {
                output += "Message Type : OobConnector";
                output += this.ServiceActivationHeader.ToString();
                output += "\nReply Channel ID : " + Convert.ToBase64String(this.ReplyChannelId);
            }
            else
            {
                output += "Message Type : OobConnectorAck";
            }

            output += "\nWFD Address : " + StringFormatter.ToAddressString(this.WFDAddress);
            output += "\nLink Local Address : " + StringFormatter.ToAddressString(this.LinkLocalAddress);
            output += "\nIPv4 Address : " + StringFormatter.ToAddressString(this.IPv4Address);
            output += "\nProximity Address : " + StringFormatter.ToAddressString(this.ProximityAddress);
            output += "\nGlobal Address : " + StringFormatter.ToAddressString(this.GlobalAddress);
            output += "\nTeredo Address : " + StringFormatter.ToAddressString(this.TeredoAddress);

            if (!this.IsAck)
            {
                output += "\nReserved Word : " + this.Reserved.ToString();
            }

            output += "\nBluetooth MAC Address : " + StringFormatter.ToAddressString(this.BluetoothMACAddress);

            if (this.WFDBlob != null)
            {
                output += "\nWFD Blob Size : " + StringFormatter.ToHexString(this.WFDBlob.Length);
                output += "\nWFD Blob : " + StringFormatter.ToHexString(this.WFDBlob);
            }
            else
            {
                output += "\nWFD Blob Size : 0x0";
            }

            return output;
        }
    }
}
